<?php
/********************************
OSBib:
A collection of PHP classes to create and manage bibliographic formatting for OS bibliography software 
using the OSBib standard.

Released through http://bibliophile.sourceforge.net under the GPL licence.
Do whatever you like with this -- some credit to the author(s) would be appreciated.

If you make improvements, please consider contacting the administrators at bibliophile.sourceforge.net 
so that your improvements can be added to the release package.

Mark Grimshaw 2007
http://bibliophile.sourceforge.net
********************************/
class UTF8
{
// Constructor
	function UTF8()
	{
	  $this->loadVars();
	}
/**
* Encode UTF-8 if not already UTF-8
*
* @version $Id: UTF8.php,v 1.7 2007/06/26 05:57:29 sirfragalot Exp $
*  Tools for validing a UTF-8 string is well formed.
*  The Original Code is Mozilla Communicator client code.
*  The Initial Developer of the Original Code is
*  Netscape Communications Corporation.
*  Portions created by the Initial Developer are Copyright (C) 1998
*  the Initial Developer. All Rights Reserved.
*  Ported to PHP by Henri Sivonen (http://hsivonen.iki.fi)
*  Slight modifications to fit with phputf8 library by Harry Fuecks (hfuecks gmail com)
* @see http://lxr.mozilla.org/seamonkey/source/intl/uconv/src/nsUTF8ToUnicode.cpp
* @see http://lxr.mozilla.org/seamonkey/source/intl/uconv/src/nsUnicodeToUTF8.cpp
* @see http://hsivonen.iki.fi/php-utf8/
* @package utf8
* @subpackage validation
*/
 
//--------------------------------------------------------------------
/**
* Tests a string as to whether it's valid UTF-8 and supported by the
* Unicode standard
* Note: this function has been modified to simple return true or false
* @author <hsivonen@iki.fi>
* @param string UTF-8 encoded string
* @return boolean true if valid
* @see http://hsivonen.iki.fi/php-utf8/
* @see utf8_compliant
* @package utf8
* @subpackage validation
*/
function smartUtf8_encode($str) {
    $mState = 0;     // cached expected number of octets after the current octet
                     // until the beginning of the next UTF8 character sequence
    $mUcs4  = 0;     // cached Unicode character
    $mBytes = 1;     // cached expected number of octets in the current sequence
 
    $len = strlen($str);
 
    for($i = 0; $i < $len; $i++) {
        $in = ord($str{$i});
        if ( $mState == 0) {
// When mState is zero we expect either a US-ASCII character or a
// multi-octet sequence.
            if (0 == (0x80 & ($in))) {
                // US-ASCII, pass straight through.
                $mBytes = 1;
            } else if (0xC0 == (0xE0 & ($in))) {
                // First octet of 2 octet sequence
                $mUcs4 = ($in);
                $mUcs4 = ($mUcs4 & 0x1F) << 6;
                $mState = 1;
                $mBytes = 2;
            } else if (0xE0 == (0xF0 & ($in))) {
                // First octet of 3 octet sequence
                $mUcs4 = ($in);
                $mUcs4 = ($mUcs4 & 0x0F) << 12;
                $mState = 2;
                $mBytes = 3;
            } else if (0xF0 == (0xF8 & ($in))) {
                // First octet of 4 octet sequence
                $mUcs4 = ($in);
                $mUcs4 = ($mUcs4 & 0x07) << 18;
                $mState = 3;
                $mBytes = 4;
            } else if (0xF8 == (0xFC & ($in))) {
/* First octet of 5 octet sequence.
*
* This is illegal because the encoded codepoint must be either
* (a) not the shortest form or
* (b) outside the Unicode range of 0-0x10FFFF.
* Rather than trying to resynchronize, we will carry on until the end
* of the sequence and let the later error handling code catch it.
*/
                $mUcs4 = ($in);
                $mUcs4 = ($mUcs4 & 0x03) << 24;
                $mState = 4;
                $mBytes = 5;
            } else if (0xFC == (0xFE & ($in))) {
                // First octet of 6 octet sequence, see comments for 5 octet sequence.
                $mUcs4 = ($in);
                $mUcs4 = ($mUcs4 & 1) << 30;
                $mState = 5;
                $mBytes = 6;
            } else {
/* Current octet is neither in the US-ASCII range nor a legal first
* octet of a multi-octet sequence.
 */
                return utf8_encode($str);
            }
        } else {
// When mState is non-zero, we expect a continuation of the multi-octet
// sequence
            if (0x80 == (0xC0 & ($in))) {
// Legal continuation.
                $shift = ($mState - 1) * 6;
                $tmp = $in;
                $tmp = ($tmp & 0x0000003F) << $shift;
                $mUcs4 |= $tmp;
/**
* End of the multi-octet sequence. mUcs4 now contains the final
* Unicode codepoint to be output
*/
                if (0 == --$mState) {
/*
 * Check for illegal sequences and codepoints.
 */
// From Unicode 3.1, non-shortest form is illegal
                    if (((2 == $mBytes) && ($mUcs4 < 0x0080)) ||
                        ((3 == $mBytes) && ($mUcs4 < 0x0800)) ||
                        ((4 == $mBytes) && ($mUcs4 < 0x10000)) ||
                        (4 < $mBytes) ||
                        // From Unicode 3.2, surrogate characters are illegal
                        (($mUcs4 & 0xFFFFF800) == 0xD800) ||
                        // Codepoints outside the Unicode range are illegal
                        ($mUcs4 > 0x10FFFF)) {
                        return utf8_encode($str);
                    }
//initialize UTF8 cache
                    $mState = 0;
                    $mUcs4  = 0;
                    $mBytes = 1;
                }
 
            } else {
/**
*((0xC0 & (*in) != 0x80) && (mState != 0))
* Incomplete multi-octet sequence.
*/
                return utf8_encode($str);
            }
        }
    }
    return $str; // $str is UTF-8
}

/**
* Encode UTF-8 if not already UTF-8
* @author Mark Grimshaw
* Adapted from user comments at php.net/en/manual
* REPLACED BY smartUtf8_encode() ABOVE
*/
	function smartUtf8_encode_old($inStr)
	{
// 'Well-formed UTF-8'
		$goodUtf8 =  '^([\x00-\x7f]|[\xc2-\xdf][\x80-\xbf]|\xe0[\xa0-\xbf][\x80-\xbf]|[\xe1-\xec][\x80-\xbf]{2}|\xed[\x80-\x9f][\x80-\xbf]|[\xee-\xef][\x80-\xbf]{2}|f0[\x90-\xbf][\x80-\xbf]{2}|[\xf1-\xf3][\x80-\xbf]{3}|\xf4[\x80-\x8f][\x80-\xbf]{2})*$';
// ISO-10646 'UCS'
		$ucsUtf8 = '^([\x00-\x7f]|[\xc0-\xdf][\x80-\xbf]|[\xe0-\xef][\x80-\xbf]{2}|[\xf0-\xf7][\x80-\xbf]{3}|[\xf8-\xfb][\x80-\xbf]{4}|[\xfc-\xfd][\x80-\xbf]{5})*$';
		if(preg_match("/$goodUtf8|$ucsUtf8/", $inStr))
			return $inStr;
		return utf8_encode($inStr);
	}

// Decode UTF-8 ONLY if the input has been UTF-8-encoded.
// Adapted from 'nospam' in the user contributions at:
// http://www.php.net/manual/en/function.utf8-decode.php
	function smartUtf8_decode($inStr)
	{
// Replace ? with a unique string
		$newStr = str_replace("?", "w0i0k0i0n0d0x", $inStr);
// Try the utf8_decode
		$newStr = $this->decodeUtf8($newStr);
// if it contains ? marks
		if(strpos($newStr, "?") !== false)
// Something went wrong, set newStr to the original string.
			$newStr = $inStr;
		else
// If not then all is well, put the ?-marks back where is belongs
			$newStr = str_replace("w0i0k0i0n0d0x", "?", $newStr);
		return $newStr;
	}
// UTF-8 encoding - PROPERLY decode UTF-8 as PHP's utf8_decode can't hack it.
// Freely borrowed from morris_hirsch at http://www.php.net/manual/en/function.utf8-decode.php
// bytes bits representation
// 1  7  0bbbbbbb
// 2  11  110bbbbb 10bbbbbb
// 3  16  1110bbbb 10bbbbbb 10bbbbbb
// 4  21  11110bbb 10bbbbbb 10bbbbbb 10bbbbbb
// Each b represents a bit that can be used to store character data.

// input CANNOT have single byte upper half extended ascii codes
function decodeUtf8 ($utf8_string) {

  $out = "";
  $ns = strlen ($utf8_string);
  for ($nn = 0; $nn < $ns; $nn++) {
   $ch = $utf8_string [$nn];
   $ii = ord ($ch);

//1 7 0bbbbbbb (127)

   if ($ii < 128) $out .= $ch;

//2 11 110bbbbb 10bbbbbb (2047)

   else if ($ii >>5 == 6) {
  $b1 = ($ii & 31);

  $nn++;
   $ch = $utf8_string [$nn];
   $ii = ord ($ch);
  $b2 = ($ii & 63);

  $ii = ($b1 * 64) + $b2;

     $ent = sprintf ("&#%d;", $ii);
     $out .= $ent;
   }

//3 16 1110bbbb 10bbbbbb 10bbbbbb

   else if ($ii >>4 == 14) {
  $b1 = ($ii & 31);

  $nn++;
   $ch = $utf8_string [$nn];
   $ii = ord ($ch);
  $b2 = ($ii & 63);

  $nn++;
   $ch = $utf8_string [$nn];
   $ii = ord ($ch);
  $b3 = ($ii & 63);

  $ii = ((($b1 * 64) + $b2) * 64) + $b3;

     $ent = sprintf ("&#%d;", $ii);
     $out .= $ent;
   }

//4 21 11110bbb 10bbbbbb 10bbbbbb 10bbbbbb

   else if ($ii >>3 == 30) {
  $b1 = ($ii & 31);

  $nn++;
   $ch = $utf8_string [$nn];
   $ii = ord ($ch);
  $b2 = ($ii & 63);

  $nn++;
   $ch = $utf8_string [$nn];
   $ii = ord ($ch);
  $b3 = ($ii & 63);

  $nn++;
   $ch = $utf8_string [$nn];
   $ii = ord ($ch);
  $b4 = ($ii & 63);

  $ii = ((((($b1 * 64) + $b2) * 64) + $b3) * 64) + $b4;

     $ent = sprintf ("&#%d;", $ii);
     $out .= $ent;
   }

  }
  return $out;
}
  function encodeUtf8($str) {
    preg_match_all("/&#([0-9]*?);/", $str, $unicode);
    foreach( $unicode[0] as $key => $value) {
      $str = preg_replace("/".$value."/", $this->code2utf8($unicode[1][$key]), $str);
    }
    return $str;
}

  /**
   * This function returns any UTF-8 encoded text as a list of
   * Unicode values:
   *
   * @author Scott Michael Reynen <scott@randomchaos.com>
   * @link   http://www.randomchaos.com/document.php?source=php_and_unicode
   * @see    unicode_to_utf8()
   */
  function utf8_to_unicode( $str ) {
    $unicode = array();  
    $values = array();
    $lookingFor = 1;
    
    for ($i = 0; $i < strlen( $str ); $i++ ) {
      $thisValue = ord( $str[ $i ] );
      if ( $thisValue < 128 ) $unicode[] = $thisValue;
      else {
	if ( count( $values ) == 0 ) $lookingFor = ( $thisValue < 224 ) ? 2 : 3;
	$values[] = $thisValue;
	if ( count( $values ) == $lookingFor ) {
	  $number = ( $lookingFor == 3 ) ?
	    ( ( $values[0] % 16 ) * 4096 ) + ( ( $values[1] % 64 ) * 64 ) + ( $values[2] % 64 ):
	    ( ( $values[0] % 32 ) * 64 ) + ( $values[1] % 64 );
	  $unicode[] = $number;
	  $values = array();
	  $lookingFor = 1;
	}
      }
    }
    return $unicode;
  }
  
  /**
   * This function converts a Unicode array back to its UTF-8 representation
   *
   * @author Scott Michael Reynen <scott@randomchaos.com>
   * @link   http://www.randomchaos.com/document.php?source=php_and_unicode
   * @see    utf8_to_unicode()
   */
  function unicode_to_utf8( $str ) {
    $utf8 = '';
    foreach( $str as $unicode ) {
      if ( $unicode < 128 ) {
	$utf8.= chr( $unicode );
      } elseif ( $unicode < 2048 ) {
	$utf8.= chr( 192 +  ( ( $unicode - ( $unicode % 64 ) ) / 64 ) );
	$utf8.= chr( 128 + ( $unicode % 64 ) );
      } else {
	$utf8.= chr( 224 + ( ( $unicode - ( $unicode % 4096 ) ) / 4096 ) );
	$utf8.= chr( 128 + ( ( ( $unicode % 4096 ) - ( $unicode % 64 ) ) / 64 ) );
	$utf8.= chr( 128 + ( $unicode % 64 ) );
      }
    }
    return $utf8;
  }
  function code2utf8($num){
    if($num<128)return chr($num);
    if($num<2048)return chr(($num>>6)+192).chr(($num&63)+128);
    if($num<65536)return chr(($num>>12)+224).chr((($num>>6)&63)+128).chr(($num&63)+128);
    if($num<2097152)return chr(($num>>18)+240).chr((($num>>12)&63)+128).chr((($num>>6)&63)+128). chr(($num&63)+128);
    return '';
  }

  /**
   * This is a unicode aware replacement for strtolower()
   *
   * Uses mb_string extension if available
   *
   * @author Andreas Gohr <andi@splitbrain.org>
   * @see    strtolower()
   * @see    utf8_strtoupper()
   */  
  function utf8_strtolower($string){
  if(function_exists('mb_strtolower'))
    return mb_strtolower($string,'utf-8');

    $uni = $this->utf8_to_unicode($string); 
    $cnt = count($uni);
    for ($i=0; $i < $cnt; $i++){
      if(array_key_exists($uni[$i], $this->UTF8_UPPER_TO_LOWER) && $this->UTF8_UPPER_TO_LOWER[$uni[$i]]){
	$uni[$i] = $this->UTF8_UPPER_TO_LOWER[$uni[$i]];
      }
    }
    return $this->unicode_to_utf8($uni);
  }

  /**
   * This is a unicode aware replacement for strtoupper()
   *
   * Uses mb_string extension if available
   *
   * @author Andreas Gohr <andi@splitbrain.org>
   * @see    strtoupper()
   * @see    utf8_strtoupper()
   */
  function utf8_strtoupper($string){
  if(function_exists('mb_strtoupper'))
    return mb_strtoupper($string,'utf-8');

    $uni = $this->utf8_to_unicode($string);
    for ($i=0; $i < count($uni); $i++){
      if(array_key_exists($uni[$i], $this->UTF8_LOWER_TO_UPPER) && $this->UTF8_LOWER_TO_UPPER[$uni[$i]]){
	$uni[$i] = $this->UTF8_LOWER_TO_UPPER[$uni[$i]];
      }
    }
    return $this->unicode_to_utf8($uni);
  }
  /**
   * This is a unicode aware replacement for substr()
   *
   * Uses mb_string extension if available
   *
   * @author Andreas Gohr <andi@splitbrain.org>
   * @see    substr()
   */
  function utf8_substr($str, $start, $length=null){
    if (function_exists('mb_substr'))
      return mb_substr($str,$start,$length,'utf-8');
    
    $uni = $this->utf8_to_unicode($str);
    return $this->unicode_to_utf8(array_slice($uni,$start,$length));
  }
  /**
   * This is a unicode aware replacement for ucfirst()
   *
   * @author Andrea Rossato <arossato@istitutocolli.org>
   * @see    ucfirst()
   */

  function utf8_ucfirst($str) {
    $fc = $this->utf8_substr($str, 0, 1);
    return $this->utf8_strtoupper($fc) . $this->utf8_substr($str, 1, $this->utf8_strlen($str));
  }
  /**
   * This is a unicode aware replacement for strlen()
   *
   * Uses mb_string extension if available
   *
   * @author Andreas Gohr <andi@splitbrain.org>
   * @see    strlen()
   */
  function utf8_strlen($string){
    if(!defined('UTF8_NOMBSTRING') && function_exists('mb_strlen'))
      return mb_strlen($string,'utf-8');
    
    $uni = $this->utf8_to_unicode($string);
    return count($uni);
  }
  
  function utf8_htmlspecialchars ($str) {
    $str = str_replace("\"", "&quot;", $str);    
    $str = str_replace("<", "&lt;", $str);    
    $str = str_replace(">", "&gt;", $str);    
    $str = preg_replace("/&(?![a-zA-Z0-9#]+?;)/", "&amp;", $str);
    return $str;
  }



  function loadVars() {
    /**
     * UTF-8 Case lookup table
     *
     * This lookuptable defines the upper case letters to their corresponding
     * lower case letter in UTF-8
     *
     * @author Andreas Gohr <andi@splitbrain.org>
     */
    $this->UTF8_LOWER_TO_UPPER = array(
  0x0061=>0x0041, 0x03C6=>0x03A6, 0x0163=>0x0162, 0x00E5=>0x00C5, 0x0062=>0x0042,
  0x013A=>0x0139, 0x00E1=>0x00C1, 0x0142=>0x0141, 0x03CD=>0x038E, 0x0101=>0x0100,
  0x0491=>0x0490, 0x03B4=>0x0394, 0x015B=>0x015A, 0x0064=>0x0044, 0x03B3=>0x0393,
  0x00F4=>0x00D4, 0x044A=>0x042A, 0x0439=>0x0419, 0x0113=>0x0112, 0x043C=>0x041C,
  0x015F=>0x015E, 0x0144=>0x0143, 0x00EE=>0x00CE, 0x045E=>0x040E, 0x044F=>0x042F,
  0x03BA=>0x039A, 0x0155=>0x0154, 0x0069=>0x0049, 0x0073=>0x0053, 0x1E1F=>0x1E1E,
  0x0135=>0x0134, 0x0447=>0x0427, 0x03C0=>0x03A0, 0x0438=>0x0418, 0x00F3=>0x00D3,
  0x0440=>0x0420, 0x0454=>0x0404, 0x0435=>0x0415, 0x0449=>0x0429, 0x014B=>0x014A,
  0x0431=>0x0411, 0x0459=>0x0409, 0x1E03=>0x1E02, 0x00F6=>0x00D6, 0x00F9=>0x00D9,
  0x006E=>0x004E, 0x0451=>0x0401, 0x03C4=>0x03A4, 0x0443=>0x0423, 0x015D=>0x015C,
  0x0453=>0x0403, 0x03C8=>0x03A8, 0x0159=>0x0158, 0x0067=>0x0047, 0x00E4=>0x00C4,
  0x03AC=>0x0386, 0x03AE=>0x0389, 0x0167=>0x0166, 0x03BE=>0x039E, 0x0165=>0x0164,
  0x0117=>0x0116, 0x0109=>0x0108, 0x0076=>0x0056, 0x00FE=>0x00DE, 0x0157=>0x0156,
  0x00FA=>0x00DA, 0x1E61=>0x1E60, 0x1E83=>0x1E82, 0x00E2=>0x00C2, 0x0119=>0x0118,
  0x0146=>0x0145, 0x0070=>0x0050, 0x0151=>0x0150, 0x044E=>0x042E, 0x0129=>0x0128,
  0x03C7=>0x03A7, 0x013E=>0x013D, 0x0442=>0x0422, 0x007A=>0x005A, 0x0448=>0x0428,
  0x03C1=>0x03A1, 0x1E81=>0x1E80, 0x016D=>0x016C, 0x00F5=>0x00D5, 0x0075=>0x0055,
  0x0177=>0x0176, 0x00FC=>0x00DC, 0x1E57=>0x1E56, 0x03C3=>0x03A3, 0x043A=>0x041A,
  0x006D=>0x004D, 0x016B=>0x016A, 0x0171=>0x0170, 0x0444=>0x0424, 0x00EC=>0x00CC,
  0x0169=>0x0168, 0x03BF=>0x039F, 0x006B=>0x004B, 0x00F2=>0x00D2, 0x00E0=>0x00C0,
  0x0434=>0x0414, 0x03C9=>0x03A9, 0x1E6B=>0x1E6A, 0x00E3=>0x00C3, 0x044D=>0x042D,
  0x0436=>0x0416, 0x01A1=>0x01A0, 0x010D=>0x010C, 0x011D=>0x011C, 0x00F0=>0x00D0,
  0x013C=>0x013B, 0x045F=>0x040F, 0x045A=>0x040A, 0x00E8=>0x00C8, 0x03C5=>0x03A5,
  0x0066=>0x0046, 0x00FD=>0x00DD, 0x0063=>0x0043, 0x021B=>0x021A, 0x00EA=>0x00CA,
  0x03B9=>0x0399, 0x017A=>0x0179, 0x00EF=>0x00CF, 0x01B0=>0x01AF, 0x0065=>0x0045,
  0x03BB=>0x039B, 0x03B8=>0x0398, 0x03BC=>0x039C, 0x045C=>0x040C, 0x043F=>0x041F,
  0x044C=>0x042C, 0x00FE=>0x00DE, 0x00F0=>0x00D0, 0x1EF3=>0x1EF2, 0x0068=>0x0048,
  0x00EB=>0x00CB, 0x0111=>0x0110, 0x0433=>0x0413, 0x012F=>0x012E, 0x00E6=>0x00C6,
  0x0078=>0x0058, 0x0161=>0x0160, 0x016F=>0x016E, 0x03B1=>0x0391, 0x0457=>0x0407,
  0x0173=>0x0172, 0x00FF=>0x0178, 0x006F=>0x004F, 0x043B=>0x041B, 0x03B5=>0x0395,
  0x0445=>0x0425, 0x0121=>0x0120, 0x017E=>0x017D, 0x017C=>0x017B, 0x03B6=>0x0396,
  0x03B2=>0x0392, 0x03AD=>0x0388, 0x1E85=>0x1E84, 0x0175=>0x0174, 0x0071=>0x0051,
  0x0437=>0x0417, 0x1E0B=>0x1E0A, 0x0148=>0x0147, 0x0105=>0x0104, 0x0458=>0x0408,
  0x014D=>0x014C, 0x00ED=>0x00CD, 0x0079=>0x0059, 0x010B=>0x010A, 0x03CE=>0x038F,
  0x0072=>0x0052, 0x0430=>0x0410, 0x0455=>0x0405, 0x0452=>0x0402, 0x0127=>0x0126,
  0x0137=>0x0136, 0x012B=>0x012A, 0x03AF=>0x038A, 0x044B=>0x042B, 0x006C=>0x004C,
  0x03B7=>0x0397, 0x0125=>0x0124, 0x0219=>0x0218, 0x00FB=>0x00DB, 0x011F=>0x011E,
  0x043E=>0x041E, 0x1E41=>0x1E40, 0x03BD=>0x039D, 0x0107=>0x0106, 0x03CB=>0x03AB,
  0x0446=>0x0426, 0x00FE=>0x00DE, 0x00E7=>0x00C7, 0x03CA=>0x03AA, 0x0441=>0x0421,
  0x0432=>0x0412, 0x010F=>0x010E, 0x00F8=>0x00D8, 0x0077=>0x0057, 0x011B=>0x011A,
  0x0074=>0x0054, 0x006A=>0x004A, 0x045B=>0x040B, 0x0456=>0x0406, 0x0103=>0x0102,
  0x03BB=>0x039B, 0x00F1=>0x00D1, 0x043D=>0x041D, 0x03CC=>0x038C, 0x00E9=>0x00C9,
  0x00F0=>0x00D0, 0x0457=>0x0407, 0x0123=>0x0122,
  );
  
    $this->UTF8_UPPER_TO_LOWER = array_flip($this->UTF8_LOWER_TO_UPPER);
  }
  
}
?>
